

#include <ctype.h>

#define DUA_CORE

#include "dua_def.h"
#include "dua_lex.h"


typedef struct {
    int tk;
    const char* name;
}Reserved;

static Reserved RESERVEDS[] = {
    {TK_BOOL, "bool"},
    {TK_TRUE, "true"},
    {TK_LET, "let"},
    {TK_FALSE, "false"},
    {TK_INT, "int"},
    {TK_LONG, "long"},
    {TK_FLOAT, "float"},
    {TK_DOUBLE, "double"},
    {TK_TSTRING, "string"},
    {TK_IF, "if"},
    {TK_ELSE, "else"},

    {TK_VALUE, "value"},
    {TK_ARITH, "arith"},
    {TK_CAST, "cast"},
    {TK_ENDIF, "endif"},
    {TK_PRINT, "print"},

    {TK_LOAD, "load"},
    {TK_POP, "pop"},
    {TK_COPY, "copy"},
    {TK_CASTB, "castb"},
    {TK_CASTI, "casti"},
    {TK_CASTL, "castl"},
    {TK_ADDI, "addi"},
    {TK_ADDL, "addl"},
    {TK_ADDF, "addf"},
    {TK_ADDD, "addd"},
    {TK_SUBI, "subi"},
    {TK_SUBL, "subl"},
    {TK_SUBF, "subf"},
    {TK_SUBD, "subd"},
    {TK_MULI, "muli"},
    {TK_MULL, "mull"},
    {TK_MULF, "mulf"},
    {TK_MULD, "muld"},
    {TK_DIVI, "divi"},
    {TK_DIVL, "divl"},
    {TK_DIVF, "divf"},
    {TK_DIVD, "divd"},
    {TK_JUMP, "jump"},
    {TK_JUMPIF, "jumpif"},
    {TK_END, "end"},
    {0, NULL},
};

#define isNewLine(lex) \
    ((lex)->c == '\n' || (lex)->c == '\r')
static inline void
save(DuaState* D, DuaLex* lex, unsigned char c){
    if(lex->saveCur >= lex->saveCap){
        size_t cap = lex->saveCap * 2;
        cap = cap ? cap : 256;
        lex->save = duaRelloc(D,
                lex->save, lex->saveCap, cap);
        lex->saveCap = cap;
    }
    lex->save[lex->saveCur ++] = c;
}

static inline void
next(DuaLex * lex){
    if(lex->dataCur >= lex->data->len){
        lex->c = TK_EOS;
        return;
    }
    lex->c = lex->data->s[lex->dataCur ++];
}
#define saveAndNext(D, LEX) save((D), (LEX), (LEX)->c); next((LEX))
#define resetSave(LEX) (LEX)->saveCur = 0

static inline int
checkNext(DuaLex* lex, int c){
    if(lex->c == c){ next(lex); return 1;}
    return 0;
}


static inline void
lexError(DuaState* D, DuaLex* lex, const char* msg) {
    char buf[128];
    int len = snprintf(buf, 128, "ctx lex error: line(%d), msg(%s)",
        lex->line, msg);
    duaRet(D, DUA_ERROR, buf);
}


static inline int
checkNext2(DuaState* D, DuaLex* lex, const char* str){
    if(lex->c == str[0] || lex->c == str[1]){
        save(D, lex, lex->c); next(lex);
        return 1;
    }else{
        return 0;
    }
}

static void
incLine(DuaLex* lex){
    int old = lex->c;
    next(lex);
    if(isNewLine(lex) && lex->c != old){
        next(lex);
    }
    lex->line ++;
}

static int
skipSep(DuaState* D, DuaLex * lex){
    int count = 0;
    int c = lex->c;
    save(D, lex, lex->c); next(lex);
    while(lex->c == '='){
        save(D, lex, lex->c); next(lex);
        count ++;
    }
    return (lex->c == c) ? count : (-count) -1;
}

static int
readLongString(DuaState* D, DuaLex* lex, DuaValue* sem, int sep){
    while(lex->c != TK_EOS){switch(lex->c){
        case '`':
            if(skipSep(D, lex) == sep){
                save(D, lex, lex->c); next(lex);
                goto endloop;
            }
            break;
        case '\n': case '\r':
            if(sem){ save(D, lex, lex->c); }
            incLine(lex);
            break;
        default:
            if(sem){ save(D, lex, lex->c);}
            next(lex);
            break;
    }}
    fprintf(stderr, "data_long_string error\n");
    duaRet(D, DUA_ERROR, "data_long_string error");
endloop:
    if(sem){
        sem->ts = duaNewStringLen(D,
            lex->save + 1, lex->saveCur - 3 - sep);
    }
    resetSave(lex);
    next(lex);
    return TK_STR;
}

static int
readString(DuaState* D, DuaLex* lex, DuaValue* sem){
    next(lex);
    int c = lex->c;
    while(c != TK_EOS){switch(lex->c){
        case '\n': case '\r':
            c = TK_EOS;
            //error
            //fprintf(stderr, "error unfinshed string\n");
            //duaRet(lex->D, DUA_ERROR, "error unfinshed string");
            break;
        case '\\':
            next(lex);
            switch(lex->c){
                case 'a': c = '\a'; break;
                case 'b': c = '\b'; break;
                case 'f': c = '\f'; break;
                case 'n': c = '\n'; break;
                case 'r': c = '\r'; break;
                case 't': c = '\t'; break;
                case 'v': c = '\v'; break;
                default: c = lex->c; break;
            }
            if(c != TK_EOS){
                save(D, lex, c); next(lex);
            }
            break;
        case '"':
            goto endloop;
        default:
            save(D, lex, lex->c); next(lex);
            break;
    }}

    fprintf(stderr, "error unfinshed string\n");
    duaRet(D, DUA_ERROR, "error unfinshed string");
endloop:
    sem->type = DUA_TSTRING;
    sem->ts = duaNewStringLen(D,
            lex->save, lex->saveCur);
    resetSave(lex);
    next(lex);
    return TK_STR;
}


static int
readNumeral(DuaState* D, DuaLex * lex, DuaValue * sem){
    checkNext2(D, lex, "-+");
    if(lex->c == '0'){
        saveAndNext(D, lex);
        checkNext2(D, lex, "xX");
    }
    while(lex->c != TK_EOS){
        if(isxdigit(lex->c)){
            saveAndNext(D, lex);
        }else if(lex->c == '.'){
            saveAndNext(D, lex);
        }else{
            break;
        }
    }
    save(D, lex, '\0');
    char * e = duaToNumber(lex->save, sem);
    if(!e){
        printf("read numeral error:%s\n", lex->save);
        duaRet(D, DUA_ERROR, "read numeral error");
    }
    resetSave(lex);
    return TK_NUMBER;
}

static int
readDefault(DuaState* D, DuaLex* lex, DuaValue* sem){
    if(isalpha(lex->c) || lex->c == '_'){
        do{
            save(D, lex, lex->c);
            next(lex);
        }while(isalnum(lex->c) || lex->c == '_');
        sem->ts = duaNewStringLen(D, lex->save, lex->saveCur);
        sem->type = DUA_TSTRING;
        resetSave(lex);
        if(sem->ts->flag){
            return sem->ts->flag;
        }else{
            return TK_NAME;
        }
    }else{
        int c = lex->c;
        next(lex);
        return c;
    }
}

static int
lexToken(DuaState* D, DuaLex* lex, DuaValue* sem){
    for(;;){switch(lex->c){
        case '\n': case '\r':
            incLine(lex); break;
        case ' ': case '\f': case '\t': case '\v':
            next(lex); break;
        case '/':
            next(lex);
            if(checkNext(lex, '/')){
                do{
                    if(checkNext(lex, TK_EOS)){
                        return TK_EOS;
                    }
                    next(lex);
                }while(!checkNext(lex, '\n'));
                lex->line ++;
                return lexToken(D, lex, sem);
            }else{
                return '/';
            }
        case '"':
            return readString(D, lex, sem);
        case '`':
            return readLongString(D, lex, sem, 0);
        case '=':
            next(lex);
            if(checkNext(lex, '=')){
                return TK_EQ;
            }else{
                return '=';
            }
        case '<':
            next(lex);
            if(checkNext(lex, '=')){
                return TK_LE;
            }else if(checkNext(lex, '<')){
                return TK_SHL;
            }else{
                return '<';
            }
        case '>':
            next(lex);
            if(checkNext(lex, '=')){
                return TK_NE;
            }else if(checkNext(lex, '>')){
                return TK_SHR;
            }else{
                return '>';
            }
        case '!':
            next(lex);
            if(checkNext(lex, '=')){
                return TK_NE;
            }else{
                return '!';
            }
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            return readNumeral(D, lex, sem);
        case -1: case '\0':
            return TK_EOS;
        default:
            return readDefault(D, lex, sem);
    }}
}


#define readTypeNumber(v, TT, CAST, sem) \
    if(sem.type == DUA_TLONG){ \
        v->TT = (CAST)sem.l; \
    }else{ \
        v->TT = (CAST)sem.d; \
    }

static void
nextNumber(DuaState* D, DuaLex* lex, DuaValue* v, int symbol) {
    DuaToken* tk = &lex->token;
    if (tk->tk != TK_NUMBER) {
        lexError(D, lex, "execpt number");
    }
    if (!checkNext(lex, ':')) {
        *v = tk->sem;
        return;
    }
    DuaValue sem = tk->sem;
    duaNextLex(D, lex);
    switch (tk->tk) {
    case TK_INT:
        v->type = DUA_TINT;
        readTypeNumber(v, i, int32_t, sem);
        v->i *= symbol;
        break;
    case TK_LONG:
        v->type = DUA_TLONG;
        readTypeNumber(v, l, int64_t, sem);
        v->l *= symbol;
        break;
    case TK_FLOAT:
        v->type = DUA_TFLOAT;
        readTypeNumber(v, f, float, sem);
        v->f *= symbol;
        break;
    case TK_DOUBLE:
        v->type = DUA_TDOUBLE;
        readTypeNumber(v, d, double, sem);
        v->d *= symbol;
        break;
    default:
        lexError(D, lex, "execpt type name");
        break;
    }
    duaNextLex(D, lex);

}

extern void
duaInitLex(DuaState* D){
    DuaString* ts;
    int i = 0;
    Reserved* r = &RESERVEDS[i];
    while (r->name) {
        ts = duaNewString(D, r->name);
        ts->flag = r->tk;
        r = &RESERVEDS[++i];
    }
}

extern void
duaNextLex(DuaState* D, DuaLex* lex){
    lex->lastLine = lex->line;
    if(lex->ahead.tk != TK_EOS){
        lex->token = lex->ahead;
        lex->ahead.tk = TK_EOS;
    }else {
        memset(&lex->token, 0, sizeof(DuaToken));
        resetSave(lex);
        lex->token.tk = lexToken(D, lex, &lex->token.sem);
    }
}

extern DuaLex*
duaNewLex(DuaState* D, DuaString* ts){
    DuaLex* lex = duaNewObj(D, 0, sizeof(DuaLex));
    lex->data = ts;
    lex->dataCur = 0;
    lex->ahead.tk = TK_EOS;
    lex->line = 1;
    lex->saveCap = 0;
    lex->saveCur = 0;
    lex->save = NULL;
    next(lex);
    return lex;
}

extern int64_t
duaLexInteger(DuaState* D, DuaLex* lex) {
    if (lex->token.tk != TK_NUMBER) {
        lexError(D, lex, "expect integer");
    }
    else if (lex->token.sem.type != DUA_TLONG) {
        lexError(D, lex, "expect integer");
    }
    int64_t l = lex->token.sem.l;
    duaNextLex(D, lex);
    return l;
}


extern void
duaLexValue(DuaState* D, DuaLex* lex, DuaValue* v) {
    DuaToken* tk = &lex->token;
    switch (tk->tk) {
    case '-':
        duaNextLex(D, lex);
        nextNumber(D, lex, v, -1);
        break;
    case '+':
        duaNextLex(D, lex);
        nextNumber(D, lex, v, 1);
        break;
    case TK_NUMBER:
        nextNumber(D, lex, v, 1);
        break;
    case TK_STR:
        *v = tk->sem;
        duaNextLex(D, lex);
        if (checkNext(lex, ':')) {
            if (!checkNext(lex, TK_TSTRING)) {
                lexError(D, lex, "expect type name 'string'");
            }
        }
        break;
    default:
        lexError(D, lex, "error value type token");
        break;
    }
}